package com.donkey.http.servlet;

import com.donkey.util.UUIDGenerator;
import com.donkey.util.WebUtils;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.FastDateFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * 文件上传
 * <p>
 * <pre>
 * == 环境配置 ==
 * p9.upload.bucket=download ///默认bucket
 * p9.upload.wsid=wsid //默认数据信息操作
 * p9.upload.home=/home/p9/upload //默认存放目录
 *
 * == 允许上传文件分类：p9.(category_name) ==
 * p9.(category_name).store-path=文件保存路径
 * p9.(category_name).thumb-path=缩略图保存路径
 * p9.(category_name).bucket=设置bucket
 * p9.(category_name).wsid=文件信息保存操作型和大小
 * p9.(category_name).home=设置存放目录
 * p9.(category_name).allow.(apk)＝允许上传文件类，单位KB
 * p9.(category_name).use-image-compress=(true/false) 是否压缩图片
 * p9.(category_name).image=压缩图片大小设置
 * p9.(category_name).thumb=缩略图片大小设置
 * p9.{category_name}.fileIdGenerator uuid,md5++
 * p9.{category_name}.fileIdCheckWsid 检测文件是否已存在(md5++)
 * p9.{category_name}.image.extensions 压缩图片大小设置
 *
 * 必要请求参数：
 * - TICKET 企业标识
 * - category 会话标识
 *
 * 可覆盖参数：
 * - file_id 文件编码(可选)
 * - file_name 文件名称(可选)
 *
 * 响应报文：
 * - result_code 结果状态
 * - result_msg 结果描述
 * - data 文件信息对象数组
 * </pre>
 *
 * @author museummjhe@foxmail.com
 * @date 2015-12-29
 */
@WebServlet(urlPatterns = "/upload")
public class UploadServlet extends HttpServlet {
	private static final long serialVersionUID = 5440638387516373847L;
	// 记录日志
	private static final Logger logger = LoggerFactory.getLogger("Upload");
	// FileUpload
	private ServletFileUpload fileUpload;
	// 上传目录格式化
	private FastDateFormat fdf = FastDateFormat.getInstance("/yyyy/MM/dd/");

	private Properties ApplicationCfg;

	@Override
	public void init(ServletConfig config) throws ServletException {
		super.init(config);
		FileItemFactory factory = new DiskFileItemFactory();
		fileUpload = new ServletFileUpload(factory);
		fileUpload.setHeaderEncoding("UTF-8");
		int sizeMax = 10485760; // 10M
		int fileSizeMax = 10485760; // 10M
		String _para = config.getInitParameter("maxSize");
		if (StringUtils.isNotBlank(_para)) {
			sizeMax = Integer.parseInt(_para);
		}
		_para = config.getInitParameter("maxFileSize");
		if (StringUtils.isNotBlank(_para)) {
			fileSizeMax = Integer.parseInt(_para);
		}
		fileUpload.setSizeMax(sizeMax);
		fileUpload.setFileSizeMax(fileSizeMax);
	}

	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
			IOException {
		resp.setStatus(200);
	}

	protected void doPost(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		Map result = new HashMap();
		result.put(COMM.RESULT_CODE, COMM.HAS_ERROR);
		try {
			// 第一次遍历上报数据项：读取和检查数据项
			List items = fileUpload.parseRequest(req);
			List fileItems = new ArrayList();
			Map params = new HashMap();
			int itemLen = items.size();
			String fileFieldName = "file";
			for (int i = 0; i < itemLen; i++) {
				Object o = items.get(i);
				FileItem file = (FileItem) o;
				if (file.isFormField()) {
					params.put(file.getFieldName(), file.getString());
				} else {
					fileFieldName = file.getFieldName();
					fileItems.add(file);
				}
			}

			String category = fileFieldName;
			// TODO

			if (StringUtils.isEmpty(category)) {
				logger.error("category 数据项为空");
				result.put(COMM.RESULT_MSG, "文件上传失败");
				WebUtils.jsonResponse(resp, result);
				return;
			}
			String storePath = category.replace('.', '/') + fdf.format(Calendar.getInstance());
			if (StringUtils.isEmpty(storePath)) {
				logger.error("category 数据项异常 ~" + category);
				result.put(COMM.RESULT_MSG, "文件上传失败");
				WebUtils.jsonResponse(resp, result);
				return;
			}

			// 提供给用户实现的过滤操作
			if (doFilter(params, req, resp) == false) {
				return;
			}

			// 上传目录
			String uploadType = "disk"; // oss
			String uploadHome = "/storage/p9/upload/";

			if (StringUtils.isEmpty(uploadHome)) {
				logger.error("p9.upload.home 参数配置不正确");
				result.put(COMM.RESULT_CODE, COMM.INTERNAL_SERVER_ERROR);
				result.put(COMM.RESULT_MSG, "服务器异常");
				WebUtils.jsonResponse(resp, result);
				return;
			}

			// 第二次遍历上报数据项：检查文档信息
			int fileLen = fileItems.size();
			if (fileLen == 0) {
				result.put(COMM.RESULT_CODE, COMM.PARAM_ERROR);
				result.put(COMM.RESULT_MSG, "请上传文件");
				WebUtils.jsonResponse(resp, result);
				return;
			}

			// 文件类型检查
			FileItem fileItem = (FileItem) fileItems.get(0);
			String fileName = fileItem.getName();
			int p = fileName.lastIndexOf('.');
			if (p == -1) {
				logger.error("上传文件类型错误");
				result.put(COMM.RESULT_MSG, "上传文件类型错误");
				WebUtils.jsonResponse(resp, result);
				return;
			}

			// 第三次遍历上报数据项：保存文档
			Map fileInfo = new HashMap();
			fileInfo.put("store_home", uploadHome);
			fileInfo.put("category_name", category);
			String fileType = fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase();

			// 结果状态列表
			try {
				byte[] bytes = fileItem.get();
				String fileId = UUIDGenerator.get();
				storePath = storePath + fileId + "." + fileType;

				fileInfo.put("file_id", fileId);
				fileInfo.put("store_path", storePath);

				saveFileInfo("", params);
				params.put(COMM.RESULT_CODE, COMM.SUCCESS);

				String fullPath = uploadHome + storePath;
				File saveFile = new File(fullPath);
				File saveDir = saveFile.getParentFile();
				if (!saveDir.exists()) {
					saveDir.mkdirs();
				}
				saveToFile(bytes, fullPath);

				onSavedFile(params, params);

				result.put(COMM.RESULT_CODE, "0");
				result.put(COMM.RESULT_MSG, "Success");
			} catch (Exception e) {
				logger.error("文档保存异常", e);
				params.put(COMM.RESULT_CODE, COMM.HAS_ERROR);
				params.put(COMM.RESULT_MSG, "文档保存异常");
			}
			result.put(COMM.RESULT_DATA, fileInfo);
			WebUtils.jsonResponse(resp, result);
		} catch (Exception e) {
			logger.error("文件上传发生异常", e);
			result.put(COMM.RESULT_CODE, COMM.INTERNAL_SERVER_ERROR);
			result.put(COMM.RESULT_MSG, "服务器异常");
			WebUtils.jsonResponse(resp, result);
		}
	}

	/**
	 * 文件保存成功后调用(请在新线程中执行回调任务，以免影响用户响应)
	 *
	 * @param params   用户信息
	 * @param fileInfo 文件信息
	 */
	protected void onSavedFile(Map params, Map fileInfo) {
	}

	/**
	 * 上传数据检查过滤
	 */
	protected boolean doFilter(Map params, HttpServletRequest req, HttpServletResponse resp)
			throws Exception {
		return true;
	}

	/**
	 * 数据另存为
	 */
	public static void saveToFile(byte data[], String outFile) throws IOException {
		FileOutputStream out = null;
		try {
			out = new FileOutputStream(outFile);
			out.write(data);
		} finally {
			IOUtils.closeQuietly(out);
		}
	}

	/**
	 * 保存文档信息，由子类实现保存文档信息到数据库中
	 *
	 * @param wsid    数据保存操作
	 * @param fileMap 上传文档信息 Map
	 * @throws Exception
	 * @return　ResultDTO
	 */
	protected Object saveFileInfo(String wsid, Map fileMap) throws Exception {
		return null;
	}

}
